﻿using Smart.Hotfix.TABLE;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace Smart.Hotfix
{
    public class EventSystem
    {
        public const int MODEL = 1;
        public const int MAIN = 2;
        public const int HOTFIX = 3;

        public int Mode
        {
            get;private set;
        }

        protected Type[] mTypes = new Type[0];
        protected Dictionary<Type, List<Type>> mAttributeToObject = new Dictionary<Type, List<Type>>(8);

        private readonly Dictionary<Type, List<IAwakeSystem>> mAwakeSystems = new Dictionary<Type, List<IAwakeSystem>>();
        private readonly Dictionary<Type, List<IStartSystem>> mStartSystems = new Dictionary<Type, List<IStartSystem>>();
        private readonly Dictionary<Type, List<IUpdateSystem>> mUpdateSystems = new Dictionary<Type, List<IUpdateSystem>>();
        private readonly Dictionary<Type, List<ILateUpdateSystem>> mLateUpdateSystems = new Dictionary<Type, List<ILateUpdateSystem>>();
        private readonly Dictionary<Type, List<IDestroySystem>> mDestroySystems = new Dictionary<Type, List<IDestroySystem>>();
        private readonly List<ITableLoader> mTableSystems = new List<ITableLoader>(64);

        private readonly Dictionary<long, Component> mAllComponents = new Dictionary<long, Component>(128);

        private readonly Queue<long> starts = new Queue<long>(128);
        private Queue<long> updates = new Queue<long>(128);
        private Queue<long> updates2 = new Queue<long>(128);
        private Queue<long> lateUpdates = new Queue<long>(128);
        private Queue<long> lateUpdates2 = new Queue<long>(128);

        public void Init(int mode,Type[] types)
        {
            Mode = mode;
            mTypes = types;
            Load();
        }

        public void Dispose()
        {
            foreach (var kv in mAllComponents)
            {
                kv.Value.Dispose();
            }
            mAllComponents.Clear();
        }

        protected void Load()
        {
            mAttributeToObject.Clear();
            for (int i = 0,max = mTypes.Length;i<max;++i)
            {
                var type = mTypes[i];
                object[] objects = type.GetCustomAttributes(false);
                for(int j = 0,maxj = objects.Length;j < maxj;++j)
                {
                    if (objects[j] is BaseAttribute baseAttribute)
                    {
                        switch (baseAttribute)
                        {
                            case ObjectSystemAttribute objectSystem:
                                this.BindObjectSystem(type);
                                break;
                            case TableLoaderAttribute tableLoaderSystem:
                                this.BindTableSystem(type);
                                break;
                            //case EventHandlerAttribute eventAttr:
                            //    this.BindEventSystem(eventAttr, type);
                            //    break;
                            //case FactoryAttribute factory:
                            //    this.BindFactory(factory, type);
                            //    break;
                        }

                        if (!mAttributeToObject.TryGetValue(baseAttribute.AttributeType, out List<Type> types))
                        {
                            types = new List<Type>(16);
                            mAttributeToObject.Add(baseAttribute.AttributeType, types);
                        }
                        types.Add(type);
                    }
                }
            }
        }

        void BindTableSystem(Type type)
        {
            object obj = Activator.CreateInstance(type);
            if(obj is ITableLoader tableLoader)
            {
                mTableSystems.Add(tableLoader);
            }
        }

        public List<ITableLoader> GetTableLoaders()
        {
            return mTableSystems;
        }

        void BindObjectSystem(Type type)
        {
            object obj = Activator.CreateInstance(type);
            switch (obj)
            {
                case IAwakeSystem awakeSystem:
                    this.mAwakeSystems.Add(awakeSystem);
                    break;
                case IStartSystem startSystem:
                    this.mStartSystems.Add(startSystem);
                    break;
                case IUpdateSystem updateSystem:
                    this.mUpdateSystems.Add(updateSystem);
                    break;
                case ILateUpdateSystem lateUpdateSystem:
                    this.mLateUpdateSystems.Add(lateUpdateSystem);
                    break;
                case IDestroySystem destroySystem:
                    this.mDestroySystems.Add(destroySystem);
                    break;
            }
        }

        public void AddComponent(Component component)
        {
            this.mAllComponents.Add(component.InstanceId, component);
            Type type = component.GetType();

            if (mStartSystems.ContainsKey(type))
            {
                this.starts.Enqueue(component.InstanceId);
            }

            if (mUpdateSystems.ContainsKey(type))
            {
                this.updates.Enqueue(component.InstanceId);
            }

            if (mLateUpdateSystems.ContainsKey(type))
            {
                this.lateUpdates.Enqueue(component.InstanceId);
            }
        }

        public void Awake(Component component)
        {
            var type = component.GetType();
            if (!mAwakeSystems.TryGetValue(type, out List<IAwakeSystem> awakeSystems))
            {
                Logger.LogError($"Awake {type} Component Failed !");
                return;
            }

            for (int i = 0,max = awakeSystems.Count;i<max;++i)
            {
                if (awakeSystems[i] is IAwake awakeSystem)
                {
                    awakeSystem.Run(component);
                }
            }
        }

        public void Awake<A>(Component component, A a)
        {
            var type = component.GetType();
            if (!mAwakeSystems.TryGetValue(type, out List<IAwakeSystem> awakeSystems))
            {
                Logger.LogError($"Awake {type} Component Failed !");
                return;
            }

            for (int i = 0, max = awakeSystems.Count; i < max; ++i)
            {
                if (awakeSystems[i] is IAwake<A> awakeSystem)
                {
                    awakeSystem.Run(component, a);
                }
            }
        }

        public void Awake<A, B>(Component component, A a, B b)
        {
            var type = component.GetType();
            if (!mAwakeSystems.TryGetValue(type, out List<IAwakeSystem> awakeSystems))
            {
                Logger.LogError($"Awake {type} Component Failed !");
                return;
            }

            for (int i = 0, max = awakeSystems.Count; i < max; ++i)
            {
                if (awakeSystems[i] is IAwake<A, B> awakeSystem)
                {
                    awakeSystem.Run(component, a, b);
                }
            }
        }

        public void Awake<A, B, C>(Component component, A a, B b, C c)
        {
            var type = component.GetType();
            if(!mAwakeSystems.TryGetValue(type,out List<IAwakeSystem> awakeSystems))
            {
                Logger.LogError($"Awake {type} Component Failed !");
                return;
            }

            for (int i = 0, max = awakeSystems.Count; i < max; ++i)
            {
                if (awakeSystems[i] is IAwake<A, B, C> awakeSystem)
                {
                    awakeSystem.Run(component, a, b, c);
                }
            }
        }

        private void Start()
        {
            while (this.starts.Count > 0)
            {
                long instanceId = this.starts.Dequeue();
                Component component;
                if (!this.mAllComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                List<IStartSystem> iStartSystems = this.mStartSystems[component.GetType()];
                if (iStartSystems == null)
                {
                    continue;
                }

                for (int i = 0, max = iStartSystems.Count; i < max; ++i)
                {
                    if(iStartSystems[i] is IStart startSystem)
                    {
                        startSystem.Run(component);
                    }
                }
            }
        }

        public void Update()
        {
            this.Start();

            while (this.updates.Count > 0)
            {
                long instanceId = this.updates.Dequeue();

                Component component;
                if (!this.mAllComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                if (component.Disposed)
                {
                    continue;
                }

                List<IUpdateSystem> iUpdateSystems = this.mUpdateSystems[component.GetType()];
                if (iUpdateSystems == null)
                {
                    continue;
                }

                this.updates2.Enqueue(instanceId);

                for (int i = 0, max = iUpdateSystems.Count; i < max; ++i)
                {
                    if (iUpdateSystems[i] is IUpdate updateSystem)
                    {
                        updateSystem.Run(component);
                    }
                }
            }

            EventSystemExtend.Swap(ref this.updates, ref this.updates2);
        }

        public void LateUpdate()
        {
            while (this.lateUpdates.Count > 0)
            {
                long instanceId = this.lateUpdates.Dequeue();

                Component component;
                if (!this.mAllComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                if (component.Disposed)
                {
                    continue;
                }

                List<ILateUpdateSystem> iLateUpdateSystems = this.mLateUpdateSystems[component.GetType()];
                if (iLateUpdateSystems == null)
                {
                    continue;
                }

                this.lateUpdates2.Enqueue(instanceId);

                for (int i = 0, max = iLateUpdateSystems.Count; i < max; ++i)
                {
                    if (iLateUpdateSystems[i] is ILateUpdate lateUpdateSystem)
                    {
                        lateUpdateSystem.Run(component);
                    }
                }
            }

            EventSystemExtend.Swap(ref this.lateUpdates, ref this.lateUpdates2);
        }

        public void Destroy(Component component)
        {
            var type = component.GetType();
            if (!this.mDestroySystems.ContainsKey(type))
            {
                return;
            }

            List<IDestroySystem> iDestroySystems = this.mDestroySystems[type];
            if (iDestroySystems == null)
            {
                return;
            }

            for (int i = 0, max = iDestroySystems.Count; i < max; ++i)
            {
                if (iDestroySystems[i] is IDestroy destroySystem)
                {
                    destroySystem.Run(component);
                }
            }
        }

        public void Remove(long instanceId)
        {
            this.mAllComponents.Remove(instanceId);
        }
    }

    public static class EventSystemExtend
    {
        public static void Add<T>(this Dictionary<Type, List<T>> systemDic,T system) where T : ISystem
        {
            var systemType = system.Type();
            if (!systemDic.TryGetValue(systemType, out List<T> systems))
            {
                systems = new List<T>(64);
                systemDic.Add(systemType, systems);
            }
            systems.Add(system);
        }

        public static void Swap<T>(ref T l,ref T r)
        {
            T tmp = l;
            l = r;
            r = tmp;
        }
    }
}